package org.moflon.boardgamegui.controller;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.moflon.boardgamegui.view.GUIField;
import org.moflon.boardgamegui.view.View;
import org.moflon.util.eMoflonEMFUtil;

/**
 * The controller handles the input events from the user interface (view), and
 * converts the event into an appropriate user action that the model
 * understands. The controller notifies the model of the user action, possibly
 * resulting in a change in the model's state.
 * [http://en.wikipedia.org/wiki/Model-view-controller]
 */
public abstract class GenericController<B extends EObject, P extends EObject, F extends EObject> {

	private B rootElement;

	private View<B, P, F> view;

	/**
	 * Returns the current instance of the board game
	 * 
	 * @return the current instance of the board game
	 */
	protected B getBoard() {
		return rootElement;
	}

	public GenericController(int width, int height) {
		newBoard(width, height);
	}

	private void newBoard(int width, int height) {
		view = View.createView(this, width, height);
		rootElement = makeNewBoard(width, height);
		if (rootElement != null) {
			initialize(getFields());
		}
		view.open();
	}

	private void initialize(EList<P> fields) {
		if (fields != null)
			for (P field : fields) {
				GUIField<B, P, F> guiField = view.getField(
						getFieldYCoordinate(field), getFieldXCoordinate(field));
				guiField.setField(field);
			}
	}

	public List<String> getFigureNames() {
		List<String> figureNames = new ArrayList<String>();
		for (EClass figureClass : getConcreteFigureClasses())
			figureNames.add(figureClass.getName());

		return figureNames;
	}

	public void createFigure(int index, GUIField<B, P, F> guiField) {
		EClass concreteFigureClass = getConcreteFigureClasses().get(index);
		@SuppressWarnings("unchecked")
		F figure = (F) getEFactory().create(concreteFigureClass);
		addFigureToField(figure, guiField.getField());
		updateView();
	}

	/**
	 * Updates the view of the board (redraw the entire board).
	 */
	public void updateView() {
		for (P field : getFields()) {
			F figure = getFigure(field);
			if (figure != null) {
				String figureTypeName = figure.eClass().getName();
				view.getField(getFieldYCoordinate(field),
						getFieldXCoordinate(field)).update(figureTypeName);
			} else
				view.getField(getFieldYCoordinate(field),
						getFieldXCoordinate(field)).update("empty");
		}
	}

	public void saveModel(String filePath) {
		eMoflonEMFUtil.saveModel(getEPackage(), rootElement, filePath);
	}

	@SuppressWarnings("unchecked")
	public void loadModel(String filePath) {
		this.rootElement = (B) eMoflonEMFUtil.loadModel(getEPackage(),
				filePath, null);

		if (view != null)
			view.close();

		view = View.createView(this, getBoardWidth(), getBoardHeight());
		initialize(getFields());
		updateView();
		view.open();
	}

	/**
	 * Returns an instance of the EPackage that represents the board game
	 * metamodel. An instance of an EPackage can be obtained by calling
	 * <code>SomePackage.eINSTANCE</code> .
	 * 
	 * @return an instance of the EPackage class that represents the board game
	 *         metamodel.
	 */
	public abstract EPackage getEPackage();

	/**
	 * Returns the width of the current board instance. <br>
	 * <br>
	 * Hint: <br>
	 * you can access the current board instance via {@link #getBoard()}.
	 * 
	 * @return the number of vertical fields in the board.
	 */
	public abstract int getBoardWidth();

	/**
	 * Returns the height of the current board instance. <br>
	 * <br>
	 * Hint: <br>
	 * you can access the current board instance via {@link #getBoard()}.
	 * 
	 * @return the number of horizontal fields in the board.
	 */
	public abstract int getBoardHeight();

	/**
	 * Returns a list containing all fields of the current board instance. <br>
	 * <br>
	 * Hint: <br>
	 * you can access the current board instance via {@link #getBoard()}.
	 * 
	 * @return an EList containing all fields.
	 */
	public abstract EList<P> getFields();

	/**
	 * Returns the figure on a given field
	 * 
	 * @param field
	 *            a given field.
	 * @return a figure if a figure is on the field, null if no figure is on the
	 *         field.
	 */
	public abstract F getFigure(P field);

	/**
	 * Returns the x-coordinate of of the given field in your current board game
	 * instance.
	 * 
	 * @param field
	 *            a field.
	 * @return the x-coordinate of the field
	 */
	public abstract int getFieldXCoordinate(P field);

	/**
	 * Returns the y-coordinate of the given field in your current board game
	 * instance.
	 * 
	 * @param field
	 *            a field.
	 * 
	 * @return the y-coordinate of the field.
	 */
	public abstract int getFieldYCoordinate(P field);

	/**
	 * Returns an instance of the EFactory class of your board game metamodel.
	 * An instance of an EFactory can be obtained by calling
	 * <code>SomeFactory.eINSTANCE</code> .
	 * 
	 * @return an instance of the EFactory class of your board game.
	 */
	public abstract EFactory getEFactory();

	/**
	 * Assigns a figure to a field on the board.
	 * 
	 * @param figure
	 *            the figure to be assigned to the field.
	 * @param field
	 *            the field the figure is to be assigned to.
	 */
	public abstract void addFigureToField(F figure, P field);

	/**
	 * Returns a list of all EClasses that represent concrete figures in your
	 * board game metamodel.<br>
	 * <br>
	 * 
	 * Hint:<br>
	 * <code>SomePackage.eINSTANCE.getEClassifiers()</code> returns a list of
	 * all <code>EClassifiers</code> in the <code>SomePackage</code>.<br>
	 * <code>SomePackage.eINSTANCE.getXYZ().isSuperTypeOf((EClass) aClassifier))</code>
	 * returns true if <code>XYZ</code> is a super type of the
	 * <code>aClassifier</code>. Caution: <code>XYZ</code> is also a super type
	 * of <code>XYZ</code>!
	 * 
	 * 
	 * @return a list of EClasses that represent all the concrete figures
	 *         specified in your metamodel.
	 * 
	 * 
	 */
	public abstract List<EClass> getConcreteFigureClasses();

	/**
	 * Instantiates a new board in the model and initializes it according to its
	 * width and height by calling an appropriate method on your model.
	 * 
	 * @param width
	 *            number of horizontal fields
	 * @param height
	 *            number of vertical fields
	 * @return an instance of a board
	 */
	public abstract B makeNewBoard(int width, int height);

	/**
	 * This method is invoked whenever a GUIField is pressed. The method is used
	 * to delegate this information to the model by calling an appropriate
	 * method on your model. <br>
	 * <br>
	 * Hint: <br>
	 * You can access the current board instance via {@link #getBoard()}.<br>
	 * 
	 * @param field
	 *            a field
	 */
	public abstract void fieldPressed(P field);
}
